package com.zhuqi.bootstrap;

import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.zhuqi.handler.*;
import com.zhuqi.properties.NettyProperties;
import com.zhuqi.protocol.MessageCodecSharable;
import com.zhuqi.protocol.ProtocolFrameDecoder;
import com.zhuqi.service.session.Session;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.timeout.IdleStateHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.stereotype.Component;

/**
 * @author ZhuQi
 * @ClassName ChatServer
 * @Date 2023/1/16 10:32
 * @Version 1.0
 * @Description netty 服务启动器
 */
@Slf4j
@Component
public class ChatServer implements ApplicationRunner, ApplicationListener<ContextClosedEvent>, ApplicationContextAware {

	/**
	 * netty 配置
	 */
	private NettyProperties nettyProperties;

	/**
	 * boss EventLoop
	 * 处理 Accept 事件
	 */
	private NioEventLoopGroup boss;

	/**
	 * worker EventLoop
	 * 处理 ReadAble WriteAble 事件
	 */
	private NioEventLoopGroup worker;

	/**
	 * 处理业务任务
	 */
	private DefaultEventLoopGroup defaultEventLoopGroup;

	/**
	 * 消息编解码器
	 */
	private MessageCodecSharable messageCodecSharable;

	/**
	 * 登陆处理器
	 */
	private LoggingRequestMessageHandel loggingRequestMessageHandle;

	/**
	 * 心跳处理器
	 */
	private HeartBeatRequestMessageHandler beatRequestMessageHandler;

	/**
	 * 登出处理器
	 */
	private LogOutRequestMessageHandel logOutRequestMessageHandel;

	/**
	 * 会话
	 */
	private static Session session;


	private ApplicationContext applicationContext;

	@Override
	public void run(ApplicationArguments args) {
		initProperties();
		Assert.isTrue(ObjectUtil.isNotEmpty(nettyProperties), "netty配置为空启动失败！");
		initAllEventLoopGroup();
		Assert.isTrue(ObjectUtil.isNotEmpty(boss) || ObjectUtil.isNotEmpty(worker) || ObjectUtil
						.isNotEmpty(defaultEventLoopGroup),
				StrUtil.format("EventLoop初始化失败boss:{}、worker:{}、loggingRequestMessageHandle:{}", boss, worker,
						defaultEventLoopGroup));
		initShareableHandler();
		Assert.isTrue(ObjectUtil.isNotEmpty(messageCodecSharable) || ObjectUtil.isNotEmpty(loggingRequestMessageHandle),
				StrUtil.format("编解码器或定长处理器初始化失败messageCodecSharable:{}、loggingRequestMessageHandle:{}",
						messageCodecSharable, loggingRequestMessageHandle));
		log.info("netty基础组件初始化成功！！");
		startNetty();
	}

	private void initProperties() {
		nettyProperties = applicationContext.getBean(NettyProperties.class);
	}


	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		this.applicationContext = applicationContext;
	}


	/**
	 * 初始化所有线程池
	 */
	private void initAllEventLoopGroup() {
		this.boss = new NioEventLoopGroup(1);
		this.worker = new NioEventLoopGroup(4);
		this.defaultEventLoopGroup = new DefaultEventLoopGroup(4);
	}

	/**
	 * 初始化共享的处理器
	 */
	private void initShareableHandler() {
		loggingRequestMessageHandle = applicationContext.getBean(LoggingRequestMessageHandel.class);
		messageCodecSharable = applicationContext.getBean(MessageCodecSharable.class);
		beatRequestMessageHandler = applicationContext.getBean(HeartBeatRequestMessageHandler.class);
		logOutRequestMessageHandel = applicationContext.getBean(LogOutRequestMessageHandel.class);
		session = applicationContext.getBean(Session.class);
	}


	private void startNetty() {
		try {
			ServerBootstrap serverBootstrap = new ServerBootstrap();
			serverBootstrap.group(boss, worker).channel(NioServerSocketChannel.class);
			// 设置半连接队列大小
			serverBootstrap.option(ChannelOption.SO_BACKLOG, 100);
			serverBootstrap.childOption(ChannelOption.SO_KEEPALIVE, true);
			serverBootstrap.childOption(ChannelOption.SO_SNDBUF, 4096);
			serverBootstrap.childOption(ChannelOption.SO_RCVBUF, 4096);
			serverBootstrap.childHandler(new ChannelInitializer<NioSocketChannel>() {
				@Override
				protected void initChannel(NioSocketChannel ch) {
					ChannelPipeline pipeline = ch.pipeline();
					pipeline.addLast(new LoggingHandler());
					pipeline.addLast(new ProtocolFrameDecoder());
					pipeline.addLast(messageCodecSharable);
					// 1 分钟闲时心跳
					pipeline.addLast(defaultEventLoopGroup, new IdleStateHandler(0, 0, 60));
					pipeline.addLast(defaultEventLoopGroup, new BasicChannelInboundHandler());
					pipeline.addLast(defaultEventLoopGroup, loggingRequestMessageHandle);
					pipeline.addLast(defaultEventLoopGroup, logOutRequestMessageHandel);
					pipeline.addLast(defaultEventLoopGroup, "chat-request", new ChatRequestMessageHandler());
					pipeline.addLast(defaultEventLoopGroup, "heartbeat-request", beatRequestMessageHandler);
				}
			});

			ChannelFuture future = serverBootstrap.bind(nettyProperties.getPort()).sync();
			if (future.isSuccess()) {
				log.info("netty-server启动成功！！监听端口：{}", nettyProperties.getPort());
			}
			// future.channel().closeFuture().sync();
		} catch (InterruptedException e) {
			log.info("netty-server发生异常:{}", e.getMessage());
		} finally {
			defaultEventLoopGroup.shutdownGracefully();
			worker.shutdownGracefully();
			boss.shutdownGracefully();
		}
	}


	/**
	 * 关闭线程池
	 */
	@Override
	public void onApplicationEvent(ContextClosedEvent event) {
		log.info("spring-boot关闭，关闭netty线程池");
		try {
			defaultEventLoopGroup.shutdownGracefully();
			worker.shutdownGracefully();
			boss.shutdownGracefully();
		} catch (Exception e) {
			log.info("关闭netty线程池失败");
			try {
				defaultEventLoopGroup.shutdownGracefully();
				worker.shutdownGracefully();
				boss.shutdownGracefully();
			} catch (Exception exception) {
				exception.printStackTrace();
			}
		}
	}


}
